﻿/*  
	========================================================================
	 awzBoxes Library
	
	 Homepage: http://www.awzhome.de/
	========================================================================
	
	This Source Code Form is subject to the terms of the Mozilla Public
	License, v. 2.0. If a copy of the MPL was not distributed with this
	file, You can obtain one at http://mozilla.org/MPL/2.0/.
   
	The Original Code is code of awzBoxes Library.

	The Initial Developer of the Original Code is Andreas Weizel.
	Portions created by the Initial Developer are
	Copyright (C) 2011-2012 Andreas Weizel. All Rights Reserved.
	
	Contributor(s): (none)
	
	========================================================================
*/

using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Collections.ObjectModel;
using System.Windows.Markup;
using System.Collections.Specialized;
using AWZhome.awzBoxes.Tools;

namespace AWZhome.awzBoxes
{
	/// <summary>
	/// Representation of a single box in a <see cref="AWZhome.awzBoxes.BoxArea"/> control.
	/// </summary>
	[ContentProperty("Items")]
	public partial class Box : UserControl
	{
		private Point _startPoint;
		private bool _highlightTopBorder;
		private bool _highlightBottomBorder;
		private bool _highlightLeftBorder;
		private bool _highlightRightBorder;

		internal const string BoxItemDragDropFormat = "BoxItemDragDropFormat";

		#region Dependency properties

		public static readonly DependencyProperty IsSideBarProperty =
			DependencyProperty.Register("IsSideBar", typeof(bool), typeof(Box), new UIPropertyMetadata(false));
		public static readonly DependencyProperty IsOverlayedOnDragOverProperty =
			DependencyProperty.Register("IsOverlayedOnDragOver", typeof(bool), typeof(Box), new UIPropertyMetadata(false));

		#endregion

		public Box()
		{
			this.Items = new ObservableCollection<BoxItem>();
			this.Items.CollectionChanged += Items_CollectionChanged;
			this.DataContext = this;
			InitializeComponent();
			this.DragOverlayBox.DataContext = this;

			// Add special event handlers
			AddHandler(UIElement.DragEnterEvent, new DragEventHandler(this.Box_DragEnter), true);
			AddHandler(UIElement.DragLeaveEvent, new DragEventHandler(this.Box_DragLeave), true);
			AddHandler(UIElement.DropEvent, new DragEventHandler(this.Box_Drop), true);
		}

		/// <summary>
		/// Returns the collection of <see cref="AWZhome.awzBoxes.BoxItem"/> elements shown
		/// in the Box.
		/// </summary>
		public ObservableCollection<BoxItem> Items
		{
			get;
			private set;
		}

		/// <summary>
		/// Determines whether the Box belongs to a side bar (<c>true</c>) or to the inner area (<c>false</c>).
		/// </summary>
		public bool IsSideBar
		{
			get
			{
				return (bool) GetValue(IsSideBarProperty);
			}
			set
			{
				SetValue(IsSideBarProperty, value);
			}
		}

		/// <summary>
		/// Determines whether a BoxItem is dragged over current Box and a popup of drop areas
		/// is shown.
		/// </summary>
		public bool IsOverlayedOnDragOver
		{
			get
			{
				return (bool) GetValue(IsOverlayedOnDragOverProperty);
			}
			set
			{
				SetValue(IsOverlayedOnDragOverProperty, value);
			}
		}

		/// <summary>
		/// Determines whether to highlight the top border of the Box.
		/// </summary>
		internal bool HighlightTopBorder
		{
			get
			{
				return _highlightTopBorder;
			}
			set
			{
				_highlightTopBorder = value;
				Thickness border = this.BorderThickness;
				border.Top = (value) ? 2 : 0;
				this.BorderThickness = border;
			}
		}

		/// <summary>
		/// Determines whether to highlight the bottom border of the Box.
		/// </summary>
		internal bool HighlightBottomBorder
		{
			get
			{
				return _highlightBottomBorder;
			}
			set
			{
				_highlightBottomBorder = value;
				Thickness border = this.BorderThickness;
				border.Bottom = (value) ? 2 : 0;
				this.BorderThickness = border;
			}
		}

		/// <summary>
		/// Determines whether to highlight the left border of the Box.
		/// </summary>
		internal bool HighlightLeftBorder
		{
			get
			{
				return _highlightLeftBorder;
			}
			set
			{
				_highlightLeftBorder = value;
				Thickness border = this.BorderThickness;
				border.Left = (value) ? 2 : 0;
				this.BorderThickness = border;
			}
		}

		/// <summary>
		/// Determines whether to highlight the right border of the Box.
		/// </summary>
		internal bool HighlightRightBorder
		{
			get
			{
				return _highlightRightBorder;
			}
			set
			{
				_highlightRightBorder = value;
				Thickness border = this.BorderThickness;
				border.Right = (value) ? 2 : 0;
				this.BorderThickness = border;
			}
		}

		/// <summary>
		/// Retrieves the <see cref="AWZhome.awzBoxes.BoxLine"/> that contains the current box.
		/// </summary>
		public BoxLine ParentBoxLine
		{
			get;
			internal set;
		}

		/// <summary>
		/// Gets/sets the internal ID of this Box.
		/// </summary>
		internal int? ID
		{
			get;
			set;
		}

		private void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
		{
			switch (e.Action)
			{
				case NotifyCollectionChangedAction.Add:
					foreach (object elem in e.NewItems)
					{
						if (elem is BoxItem)
						{
							((BoxItem) elem).ParentBox = this;
							((BoxItem) elem).IsActive = false;
							if (this.ParentBoxLine != null)
							{
								if (this.ParentBoxLine.ParentBoxArea != null)
								{
									((BoxItem) elem)._Direction = this.ParentBoxLine.ParentBoxArea.Direction;
								}
							}
						}
					}
					break;

				case NotifyCollectionChangedAction.Move:
					break;

				case NotifyCollectionChangedAction.Remove:
					foreach (object elem in e.OldItems)
					{
						if (elem is BoxItem)
						{
							BoxItem boxItem = (BoxItem) elem;
							boxItem.ParentBox = null;
							if (boxItem.ParentAutoHiddenBox == null)
							{
								boxItem._Direction = BoxItemDockDirection.NotDocking;
							}
							if ((boxItem.IsActive) && (this.Items.Count > 0))
							{
								this.BoxItemTabControl.SelectedIndex = 0;
							}
						}
					}
					break;

				case NotifyCollectionChangedAction.Replace:
					foreach (object elem in e.OldItems)
					{
						if (elem is BoxItem)
						{
							BoxItem boxItem = (BoxItem) elem;
							boxItem.ParentBox = null;
							if (boxItem.ParentAutoHiddenBox == null)
							{
								boxItem._Direction = BoxItemDockDirection.NotDocking;
							}
							if ((boxItem.IsActive) && (this.Items.Count > 0))
							{
								this.BoxItemTabControl.SelectedIndex = 0;
							}
						}
					}
					break;

				case NotifyCollectionChangedAction.Reset:
					break;

				default:
					break;
			}

			// If there are no BoxItems in here, remove this Box from its container -> this should destroy it
			if (this.Items.Count == 0)
			{
				if (this.ParentBoxLine != null)
				{
					bool deleteMe = true;
					if (!this.IsSideBar && (this.ParentBoxLine.Boxes.Count == 1) && (this.ParentBoxLine.ParentBoxArea != null))
					{
						if (this.ParentBoxLine.ParentBoxArea.BoxLines.Count == 1)
						{
							// This is the last BoxLine in the inner area, don't delete it
							deleteMe = false;
							// The active BoxItem will be "null" now
							this.ParentBoxLine.ParentBoxArea.ActiveBoxItem = null;
						}
					}

					if (deleteMe)
					{
						this.ParentBoxLine.Boxes.Remove(this);
					}
				}

				// Hide the TabControl
				this.BoxItemTabControl.Visibility = System.Windows.Visibility.Collapsed;
			}
			else
			{
				// Show the TabControl
				this.BoxItemTabControl.Visibility = System.Windows.Visibility.Visible;
			}
		}

		private void TabControl_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			// Store the mouse position
			_startPoint = e.GetPosition(null);
		}

		private void TabControl_MouseMove(object sender, MouseEventArgs e)
		{
			// Get the current mouse position
			Point mousePos = e.GetPosition(null);
			Vector diff = _startPoint - mousePos;

			//if ((e.LeftButton == MouseButtonState.Pressed) &&
			//   Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance &&
			//   Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
			if (e.LeftButton == MouseButtonState.Pressed)
			//if ((e.LeftButton == MouseButtonState.Pressed) &&
			//   Math.Abs(diff.X) > 2 &&
			//   Math.Abs(diff.Y) > 2)
			{
				// Get the dragged TabItem
				TabItem tabItem = null;
				if (sender is TabControl)
				{
					// Dragging a tab
					tabItem = ControlFinder.FindAncestor<TabItem>((DependencyObject) e.OriginalSource);
				}
				else if (sender is Label)
				{
					// Dragging the title
					tabItem = this.BoxItemTabControl.ItemContainerGenerator.ContainerFromItem(this.BoxItemTabControl.SelectedItem) as TabItem;
				}
				if (tabItem != null)
				{
					// Find the data behind the TabItem
					BoxItem boxItem = (BoxItem) this.BoxItemTabControl.ItemContainerGenerator.ItemFromContainer(tabItem);

					// Initialize the drag & drop operation
					DataObject dragData = new DataObject(BoxItemDragDropFormat, boxItem);
					DragDrop.DoDragDrop(tabItem, dragData, DragDropEffects.Move);
				}
			}
		}

		private void Box_Drop(object sender, DragEventArgs e)
		{
			if (e.Data.GetFormats().Contains(BoxItemDragDropFormat))
			{
				TabItem targetTabItem = ControlFinder.FindAncestor<TabItem>((DependencyObject) e.OriginalSource);
				if (targetTabItem == null)
				{
					if (ControlFinder.FindAncestor<Label>((DependencyObject) e.OriginalSource) != null)
					{
						targetTabItem = this.BoxItemTabControl.ItemContainerGenerator.ContainerFromItem(this.BoxItemTabControl.SelectedItem) as TabItem;
					}
				}

				if (targetTabItem != null)
				{
					BoxItem targetBoxItem =
						(BoxItem) this.BoxItemTabControl.ItemContainerGenerator.ItemFromContainer(targetTabItem);
					BoxItem sourceBoxItem = e.Data.GetData(BoxItemDragDropFormat) as BoxItem;

					// Remove any drag&drop highlighting
					targetBoxItem.IsMouseDraggedOver = false;
					sourceBoxItem.IsMouseDraggedOver = false;

					if (sourceBoxItem != targetBoxItem)
					{
						// Only move, if the source BoxItem is allowed to be dragged into this BoxArea!
						if ((sourceBoxItem.AllowedDockAreas & targetBoxItem.Direction) == targetBoxItem.Direction)
						{
							// Everything is ok, move the BoxItem
							int targetIndex = this.Items.IndexOf(targetBoxItem);
							sourceBoxItem.ParentBox.Items.Remove(sourceBoxItem);
							this.Items.Insert(targetIndex, sourceBoxItem);
							this.BoxItemTabControl.SelectedItem = sourceBoxItem;
							sourceBoxItem.IsActive = true;
						}
					}
				}

				this.IsOverlayedOnDragOver = false;
			}
		}

		private void Box_DragEnter(object sender, DragEventArgs e)
		{
			if (e.Data.GetFormats().Contains(BoxItemDragDropFormat))
			{
				BoxItem sourceBoxItem = e.Data.GetData(BoxItemDragDropFormat) as BoxItem;
				TabItem targetTabItem = ControlFinder.FindAncestor<TabItem>((DependencyObject) e.OriginalSource);
				if (targetTabItem != null)
				{
					BoxItem targetBoxItem =
						(BoxItem) this.BoxItemTabControl.ItemContainerGenerator.ItemFromContainer(targetTabItem);

					// If this is not the dragged item, highlight it
					if (targetBoxItem != sourceBoxItem)
					{
						// Only highlight, if the source BoxItem is allowed to be dragged into this BoxArea!
						if ((sourceBoxItem.AllowedDockAreas & targetBoxItem.Direction) == targetBoxItem.Direction)
						{
							targetBoxItem.IsMouseDraggedOver = true;
						}
					}
				}
				else
				{
					if (sourceBoxItem != null)
					{
						this.ConfigDragOverlayBox(sourceBoxItem);
					}
					this.IsOverlayedOnDragOver = true;
				}
			}
		}

		private void Box_DragLeave(object sender, DragEventArgs e)
		{
			if (e.Data.GetFormats().Contains(BoxItemDragDropFormat))
			{
				TabItem targetTabItem = ControlFinder.FindAncestor<TabItem>((DependencyObject) e.OriginalSource);
				if (targetTabItem != null)
				{
					BoxItem targetBoxItem =
						(BoxItem) this.BoxItemTabControl.ItemContainerGenerator.ItemFromContainer(targetTabItem);
					targetBoxItem.IsMouseDraggedOver = false;
				}

				this.IsOverlayedOnDragOver = false;
			}
		}

		/// <summary>
		/// Configures the possible dock direction drop areas in the <see cref="AWZhome.awzBoxes.Tools.DragOverlayBox"/>.
		/// </summary>
		/// <param name="boxItem">Dragged <see cref="AWZhome.awzBoxes.BoxItem"/> to use for config.</param>
		private void ConfigDragOverlayBox(BoxItem boxItem)
		{
			BoxItemDockDirection innerArea = BoxItemDockDirection.DockInnerArea;
			BoxWindowContainer parentBoxWindowContainer = null;
			BoxArea parentBoxArea = null;
			BoxLine parentBoxLine = this.ParentBoxLine;
			if (parentBoxLine != null)
			{
				parentBoxArea = parentBoxLine.ParentBoxArea;
				if (parentBoxArea != null)
				{
					innerArea = parentBoxArea.Direction;
					if (parentBoxArea.ParentBoxWindowContainer != null)
					{
						parentBoxWindowContainer = parentBoxArea.ParentBoxWindowContainer;
					}
				}
			}

			if ((parentBoxLine != null) && (parentBoxArea != null) && (parentBoxWindowContainer != null))
			{
				this.DragOverlayBox.AllowOuterTopDrop =
					(!this.IsSideBar && (parentBoxWindowContainer.TopBoxArea != null)
					&& ((boxItem.AllowedDockAreas & BoxItemDockDirection.DockTop) == BoxItemDockDirection.DockTop)
					&& (parentBoxArea.BoxLines.IndexOf(parentBoxLine) == 0));
				this.DragOverlayBox.AllowOuterBottomDrop =
					(!this.IsSideBar && (parentBoxWindowContainer.BottomBoxArea != null)
					&& ((boxItem.AllowedDockAreas & BoxItemDockDirection.DockBottom) == BoxItemDockDirection.DockBottom)
					&& (parentBoxArea.BoxLines.IndexOf(parentBoxLine) == parentBoxArea.BoxLines.Count - 1));
				this.DragOverlayBox.AllowOuterLeftDrop =
					(!this.IsSideBar && (parentBoxWindowContainer.LeftBoxArea != null)
					&& ((boxItem.AllowedDockAreas & BoxItemDockDirection.DockLeft) == BoxItemDockDirection.DockLeft)
					&& (parentBoxLine.Boxes.IndexOf(this) == 0));
				this.DragOverlayBox.AllowOuterRightDrop =
					(!this.IsSideBar && (parentBoxWindowContainer.RightBoxArea != null)
					&& ((boxItem.AllowedDockAreas & BoxItemDockDirection.DockRight) == BoxItemDockDirection.DockRight)
					&& (parentBoxLine.Boxes.IndexOf(this) == parentBoxLine.Boxes.Count - 1));
				this.DragOverlayBox.AllowInnerDrop = ((boxItem.AllowedDockAreas & innerArea) == innerArea);

				bool showInnerDropAreas = ((boxItem.AllowedDockAreas & innerArea) == innerArea)
					&& (((boxItem.ParentBox != this) && (this.Items.Count > 0)) || ((boxItem.ParentBox == this) && (this.Items.Count > 1)));
				this.DragOverlayBox.AllowInnerTopDrop = showInnerDropAreas;
				this.DragOverlayBox.AllowInnerBottomDrop = showInnerDropAreas;
				this.DragOverlayBox.AllowInnerLeftDrop = showInnerDropAreas;
				this.DragOverlayBox.AllowInnerRightDrop = showInnerDropAreas;
			}
		}

		private void BoxItemCloseCommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
		{
			e.CanExecute = true;
		}

		private void BoxItemCloseCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
		{
			BoxItem targetBoxItem = null;
			TabItem targetTabItem = ControlFinder.FindAncestor<TabItem>((DependencyObject) e.OriginalSource);
			if (targetTabItem != null)
			{
				// The original source is on a TabItem -> close it
				targetBoxItem =
					(BoxItem) this.BoxItemTabControl.ItemContainerGenerator.ItemFromContainer(targetTabItem);
			}
			else
			{
				if (ControlFinder.FindAncestor<Label>((DependencyObject) e.OriginalSource) == this.BoxTitleLabel)
				{
					// The original source is on the title label -> close active item in Box
					targetBoxItem = this.BoxItemTabControl.SelectedItem as BoxItem;
				}
			}

			if ((targetBoxItem != null) && (targetBoxItem.IsClosable))
			{
				// Close the BoxItem
				targetBoxItem.Close();
			}
		}

		private void BoxItemFloatingCommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
		{
			e.CanExecute = true;
		}

		private void BoxItemFloatingCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
		{
			BoxItem targetBoxItem = null;
			TabItem targetTabItem = ControlFinder.FindAncestor<TabItem>((DependencyObject) e.OriginalSource);
			if (targetTabItem != null)
			{
				// The original source is on a TabItem -> "float" it
				targetBoxItem =
					(BoxItem) this.BoxItemTabControl.ItemContainerGenerator.ItemFromContainer(targetTabItem);
			}
			else
			{
				if (ControlFinder.FindAncestor<Label>((DependencyObject) e.OriginalSource) == this.BoxTitleLabel)
				{
					// The original source is on the title label -> "float" active item in Box
					targetBoxItem = this.BoxItemTabControl.SelectedItem as BoxItem;
				}
			}

			if ((targetBoxItem != null)
				&& ((targetBoxItem.AllowedDockAreas & BoxItemDockDirection.Floating) == BoxItemDockDirection.Floating))
			{
				// Close the BoxItem
				targetBoxItem.Direction = BoxItemDockDirection.Floating;
			}
		}

		private void BoxItemTabControl_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
		{
			ContextMenuRequestSource source = ContextMenuRequestSource.BoxItemTab;
			BoxItem targetBoxItem = null;
			TabItem targetTabItem = ControlFinder.FindAncestor<TabItem>((DependencyObject) e.OriginalSource);
			if (targetTabItem != null)
			{
				// The original source is on a TabItem -> close it
				targetBoxItem =
					(BoxItem) this.BoxItemTabControl.ItemContainerGenerator.ItemFromContainer(targetTabItem);
			}
			else
			{
				if (ControlFinder.FindAncestor<Label>((DependencyObject) e.OriginalSource) == this.BoxTitleLabel)
				{
					// The original source is on the title label -> close active item in Box
					targetBoxItem = this.BoxItemTabControl.SelectedItem as BoxItem;
					source = ContextMenuRequestSource.BoxItemTitle;
				}
			}

			if (targetBoxItem != null)
			{
				// Close the BoxItem
				targetBoxItem.OnContextMenuRequested(source);
			}
		}

		private void UserControl_GotFocus(object sender, RoutedEventArgs e)
		{
			BoxItem activeBoxItem = this.BoxItemTabControl.SelectedItem as BoxItem;
			if (activeBoxItem != null)
			{
				activeBoxItem.IsActive = true;
			}
		}

		private void BoxItemTabControl_PreviewKeyUp(object sender, KeyEventArgs e)
		{

		}

		private void BoxItemTabControl_PreviewKeyDown(object sender, KeyEventArgs e)
		{
			if ((e.Key == Key.Tab)
				&& (Keyboard.IsKeyDown(Key.LeftCtrl)) || (Keyboard.IsKeyDown(Key.RightCtrl)))
			{
				e.Handled = true;
			}
		}

		private void BoxAutoHideCommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
		{
			e.CanExecute = true;
		}

		private void BoxAutoHideCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
		{
			BoxItem targetBoxItem = null;
			if (ControlFinder.FindAncestor<Label>((DependencyObject) e.OriginalSource) == this.BoxTitleLabel)
			{
				// The original source is on the title label -> auto-hide active item in Box
				targetBoxItem = this.BoxItemTabControl.SelectedItem as BoxItem;
			}

			if ((targetBoxItem != null) && (targetBoxItem.IsAutoHideAllowed))
			{
				// Auto-hide the item
				BoxLine parentBoxLine = this.ParentBoxLine;
				if (parentBoxLine != null)
				{
					BoxArea parentBoxArea = parentBoxLine.ParentBoxArea;
					if (parentBoxArea != null)
					{
						parentBoxArea.AutoHideBox(this);
					}
				}
			}
		}
	}
}
